/**
 *	The NeuroCoSA Toolkit
 *	Copyright (C) 2003-6 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween(mk2)
 * @license	LGPL
 */
package org.stumeikle.NeuroCoSA;

/* Comments:
// convert to NeuroCosa MK2
*/

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.event.*;

/** 
 * One of the fundemental classes used in learning in neurocosa.
 * Synapses form the connetions between neurons. They can store a template which they use
 * to determine whether an incoming signal (vesicle+signal strenght) should be propagated.
 * They can modify signals during propagation. Synapses can also connect to other synapses
 * as well as neurons.
 * 20050620 They are STATELESS --> 20060725 (but derived forms are now stateful...)
 */
public class Synapse
{
    private static final int	output_is_neuron=0;
    private static final int	output_is_synapse=1;
    private static final double	convergence_const = 0.8;/** sets the time before the connection
    							 * strength converges on the success/attempt ratio.
							 * also determines strength of connection after
							 * a single first success 
							 */
    private static final double confidence_point = 0.3; /** once the modified error is below this value
    							 * we accept the s/a ratio as correct
							 * (and drop poor connections etc)
							 */

    private    	Object		iOutputObject; 		/** neuron or synapse that this synapse connects to */
    private	int		iOutputObjectType; 	/** has value 0=neuron 1=synapse */
    private    	Vesicle		iOutputVesicle; 	/** output vesicle which is given to connected neuron
							 *  or synapse */
    private    	double		iOutputSignal; 		/** signal strength of the output */
    private	double		iRetSignal; 		/** ?? */
    private	Neuron		iParent; 		/** Who owns this synapse. (Which neuron) */

    private    	int		iOutputNeuronState; 	/** The state of the (?) neuron which we are the 
							 *  output of! danger*/
    //private    	boolean		iUpdateConnection;	/** Should this synapse update its connection strength
    //*  (?) */
    private    	boolean		iRequestDisconnect;	/** Is this synapse requesting disconnection from 
    						    	 *  its connected n or s */
    private    	boolean		iAdaptive; 		/** shoudl this synapse change its connection strength
							 *  or remain forever connected?*/
    private	long		iCreationTime;		/** when was this class created (brain time)*/
    
    protected	double		iConnectionStr; 	/** the connection strength */
    private  	double		iSuccessCount,  	/** working var for updating the connection strength */
    				iAttemptCount,  	/** working var for updating the connection strength */
    				iFailureCount;  	/** working var for updating the connection strength. 
							 *  new 20060915*/
    //20040228 controller vars:
    private	int		iNumControllers;	/** how many entities (many synapses in this case) 
							 *  are connected to this synapse. Other synapses can 
							 *  bond to this synapse and can modify or block its 
							 *  transmissions */
    private	LinkedList<Synapse> iControllingSynapseL; /** list of entities which are connected to this synapse*/
    private	String		iDbg;

    /** set the value of the output */
    private    	void	setOutputVesicle(Vesicle v) 
    {
    	iOutputVesicle=v; //@CHECK should be a copy??
    }

    /** set the output signal strength */
    private	void	setOutputSignal(double s)
    {
    	iOutputSignal = s;
    }

    /** request a disconnection from whatever the synapse is connected to . parent (ie input) neuron 
     * remains connected however.
     */ 
    public   void	requestDisconnect()
    {
        // this method is called by the derived synapses...
    	iRequestDisconnect=true;

	//force the controllers to disconnect too...
	ListIterator i = iControllingSynapseL.listIterator(0);

	for(;i.hasNext();)
	{
	    Synapse	sin = (Synapse) i.next();

	    sin.requestDisconnect();
        }
    }

    /** i don't remember.... */
    protected   void	setRetSignal( double rs )
    {
        iRetSignal = rs * iConnectionStr; //20031130
    }

    /** Take the given vesicle and create a vesicle which is to be propagated out of the
     *  synapse. This is a virtual method and should be overloaded by the users specific synapse
     *  implementation.
     * @param vp the vesicle to process
     */
    protected   Vesicle	propagate(Vesicle vp )  //virtual
    {
        //overload this function!!!
	//IMPLEMENTATIONS OF THIS MUST SET THE RETSIGNAL VAR !!!! WARNING @
	System.out.println("Synapse::propagate>>warning, pls overload propagate!");
        iRetSignal = 0.0;
	return null;
    }

    /** virtual method. user to define the exact behaviour. This method is called 
     *  when the connection between the syapse and the entity it connects to should be updated
     * @param propped did the vesicle get propped across the synapse ? 
     */
    protected   void	updateConnectionStrength(boolean propped){}     //virtual

    /** Constructor. Creation point for synapses which bind to neurons.
     * @param p the parent neuron
     * @param n the neuron this synapse should connect to
     */
    public Synapse( Neuron p, Neuron n )
    {
	iParent			= p;
	constructor();
	iOutputObject 		= (Object) n;
	iOutputObjectType	= output_is_neuron;
	((Neuron)iOutputObject).connectInput( this );
    }

    /** creation point for synapses which bind to synapses
     * @param p the parent neuron
     * @param s the synapse this synapse connects to 
     */
    public Synapse( Neuron p, Synapse s )
    {
	iParent			= p;
        constructor();
	iOutputObject		= s;
	iOutputObjectType	= output_is_synapse;
	((Synapse)iOutputObject).connectController(this);
    }

    private	void	constructor()
    {
    	iRequestDisconnect 	= false;
	iOutputVesicle		= null;
	iOutputNeuronState	= Neuron.state_inactive;
	iAdaptive		= true;
	//iUpdateConnection	= false;
	//no_joy_count		= 0;
	iOutputSignal		= 0.0;
	iRetSignal		= 0.0;
	iSuccessCount=iAttemptCount=1;
	iFailureCount=0;
	
	calculateConnectionStrength();
	iNumControllers		=0;
	iControllingSynapseL   = new LinkedList<Synapse>();
	iCreationTime		= iParent.getLayer().getBrainTime();
	iDbg 			= new String("" + this);
	//chop the package name from the string
	try{	iDbg = iDbg.replaceFirst("org.stumeikle.NeuroCoSA.", "");
	}catch(Exception e){}
    }

    /** Destructor. this can be overloaded in the specific derived forms. stumeikle 20060606
     */
    public 	void	destructor()
    {
        //must be called ....
	//now we have two cases, depending on what we're connected to.
	if (iOutputObjectType == output_is_synapse)
	{
	    ((Synapse)iOutputObject).disconnectController(this);
	}else if (iOutputObjectType == output_is_neuron)
	{
	    ((Neuron)iOutputObject).disconnectInput(this);
	}
    }

    /** 
     * The update function, called by the parent neuron. 
     * Algorithm here is: (1) synapse decides whether to propagate info from this vesicle
     * pacakge. Propagation will be modified if there are other synapses connected to this one.
     * During propagation call the user-specified propagate function to transform the input
     * vesicle package into an output vesicle + signal. Update the connection strength as nec.
     *
     * @param vp the vesicle package for propagation passed in by the parent neuron
     */
    public	void 	update( Vesicle vp ) // no update
    {
        //decide if we want to (A) respond to this vesicle and (B) propagate
        //anything.

	//Out of date(?) --> if we are inhibited then we aren't updated.
	//set the response
    	//force the response to be negative if the synapse benefit is -ve.
    	//ie if we inhibit then another synapse should be created
	//<--- o.o.d.
	
	iRetSignal=0.0;

    	//propagate...
	//20040228 stumeikle	but only if we are allowed to by presence of an active controllers
	//			IE return null if controllers are all off
	//			in future we could sum signals and behave more like a
	//			little neuron.
	if (iNumControllers>0)
	{
	    boolean	all_controllers_off = true;
	    ListIterator i = iControllingSynapseL.listIterator(0);

	    for(;i.hasNext();)
	    {
	        Synapse	sin = (Synapse) i.next();

		//dogsex: my note: maybe we should ignore DRIVE_VESICLEs here...
		if (sin.getOutputSignal()>0)
		{
		    all_controllers_off=false;
		    break;
		}
	    }
	    if (all_controllers_off==true) //IE if none of the contexts are activated
	    {
System.out.println("Synapse: All controllers off , blocking synapse!!!");
		reset();
	        return;
	    }
	}

	//20050824 - check if the neuron we're connected to is dead
	Neuron	 n = getOutputNeuron(); 
	if (n!=null)
	{
	    if (n.getState()==Neuron.state_dead)
	    {
	    	requestDisconnect();
		reset();
		return;
	    }
	}
	
	Vesicle  propped=propagate(vp);

    	if (propped!=null)
    	{
	    setOutputVesicle(propped);
//REMOVE? signal strength is contained within the vesicle now
	    setOutputSignal(iRetSignal);
        }
        else
    	    reset();

        //if we are iAdaptive, then update the benefit calculation and
        //make sure that the output strength is modified by the stored benefit
        if (iAdaptive==true)
        {
	    if (propped!=null)
	    {
	        setOutputSignal( getOutputSignal() * iConnectionStr ); //checkme
	        //stumeikle : are these ever used ???
		updateConnectionStrength(true);
	    } else
	    	updateConnectionStrength(false);
        }
    }

    public	void	reset() //virtual
    {
        //iParent neuron is switching off, we should shut down too.
	//no_joy_count = 0;
    	iOutputVesicle=null;
    	iOutputSignal=0.0;
    	iOutputNeuronState = Neuron.state_inactive;
    }

    public	void	setAdaptive(boolean b)
    {
    	iAdaptive=b;
	if (iAdaptive == false)
	{
	    iSuccessCount = iAttemptCount = 1;
	    iFailureCount = 0;
	    iConnectionStr = 1.0;
	}
    }

    public	boolean	getAdaptive()
    { return iAdaptive; }

    public	boolean	requestsDisconnect()
    {
    	return iRequestDisconnect;
    }

    public	void	setOutputNeuronState( int s )
    {
    	iOutputNeuronState = s;
    }

    public 	int	getOutputNeuronState()
    {
    	return iOutputNeuronState;
    }

    public	Vesicle    getOutputVesicle()  // DANGER DANGER
    {
    	return iOutputVesicle;
    }

    public	double	getOutputSignal()      // DANGER DANGER
    {
    	return iOutputSignal;
    }

    public	Neuron	getOutputNeuron() 	// DANGER DANGER
    {
        if (iOutputObjectType == output_is_neuron)
    	    return (Neuron)iOutputObject;
	else
	    return null;
    }

    public	Synapse	getOutputSynapse() 	// DANGER DANGER
    {
        if (iOutputObjectType == output_is_synapse)
    	    return (Synapse)iOutputObject;
	else
	    return null;
    }

    public	int	getNumControllers()
    { return iNumControllers; }
    
    public	long	getCreationTime()
    {
        return iCreationTime;
    }

    public	Neuron	getParent()
    {	return iParent; }

    public	double	getConnectionStr()
    {
        return iConnectionStr;
    }

    // 20031130 stumeikle trial tidy up stuff ...
    public void	strengthenConnection()
    {
        //somethinig was successful! improve the connection strength
        //successful
	//new algorithm 20050611 stumeikle
	if (iAdaptive==true)
	{
            iSuccessCount++;
            iAttemptCount++;
	    
	    calculateConnectionStrength();
	}
    }
    
    public void	driveFailure()
    {
        if (iAdaptive==true)
        {
            iFailureCount++;
            iAttemptCount++;
            calculateConnectionStrength();
        }
    }
    
    
    public void	forceConnectionStrengthen()
    {
        iSuccessCount++;
        iAttemptCount++;
	    
	calculateConnectionStrength();
    }

    public void	forceConnectionWeaken()
    {
        iAttemptCount++;
	    
	calculateConnectionStrength();
    }
    
    public    void	weakenConnection()
    {
        if (iAdaptive==true)
	{
            iAttemptCount++;
            
	    calculateConnectionStrength();
        }
    }



    void	calculateConnectionStrength()
    {
    	//new algorithm 20050611 stumeikle
    	double	cs,e; //e is error
	
	 cs = (iSuccessCount-iFailureCount) / iAttemptCount;
	 e  = convergence_const / Math.sqrt( iAttemptCount );
	 cs = cs - e;
	 
	 //temp change
// orig:
	 if (cs <= 0.01 && cs>=-0.01)
	 {
	 	//System.out.println("***** synapse error = " +e );
	 	if (e< confidence_point) 
		{
		    //if the error is low and the connection weak ,then remove it
		    //remove the connection
		    //System.out.println("Removing Synapse ********");
		    requestDisconnect();
		    cs = 0.0;
		}else
		{
		    //just set to something sensible
		    cs  = 0.0;
		}
	 }

         iConnectionStr=cs;
    }
    
    public	void	modifyConnectionStrByFactor( double w )
    {
	iSuccessCount = iSuccessCount * w ;
System.out.println(" ** SURPRISE: w = " + w + " setting success to " + iSuccessCount);
	calculateConnectionStrength();
    }

    public	void	modifyConnectionStrBySum( double w )
    {
    	//reduce the connection strength by a scalar quantity. strange to add
    	//statistical meaning to this. stranger still because success count is an
    	//ever increasing quantity
    
        if (w>0)	iSuccessCount += w;
        else		iFailureCount -= w;

System.out.println(" ** SURPRISE: w = " + w + " setting success to " + iSuccessCount);
	calculateConnectionStrength();
    }


    //-------------------------------------------------------------
    // Interface to connecting controllers
    //-------------------------------------------------------------
    private	void	connectController(Synapse boss)
    {
        iNumControllers++;
	iControllingSynapseL.add(boss);
    }

    private	void	disconnectController(Synapse boss)
    {
        ListIterator i = iControllingSynapseL.listIterator(0);

	for(;i.hasNext();)
	{
	    Synapse	sin = (Synapse) i.next();

	    if (sin == boss)
	    {
		i.remove();
		iNumControllers--;
		return;
	    }
        }

	System.out.println("Error, trying to disconnect controller which isnt connected!");
    }
    //----------------------------------------------------------------------------------

    public	String	getDEBUGLabel()	
    {
	return iDbg;
    }
}

